feat(api): serve /predict frames from the local filesystem#49
Merged
Conversation
…-frames # Conflicts: # api/README.md # api/src/temporal_model/api/settings.py # api/tests/test_schemas.py # api/tests/test_settings.py
MateoLostanlen
approved these changes
Jun 11, 2026
…-frames # Conflicts: # api/README.md # api/tests/test_app.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lets
/predictserve frames from a local shared volume (edge deployments) instead of S3, per the design indocs/specs/2026-06-11-api-local-frames-design.md.source: "s3" | "local"field onPredictRequest, defaulting to the newTEMPORAL_API_FRAME_SOURCEsetting (defaults3) — zero behavior change for existing deployments and callers.framesare relative paths resolved under the settings-onlyTEMPORAL_API_FRAMES_ROOTwith a symlink-aware containment check; the root is never request-suppliable by design.local_resolveinstead ofs3_fetch; resolution runs off the event loop like the S3 fetch).FRAME_SOURCE=localwithoutFRAMES_ROOTfails at startup; a root that is not a directory at request time is a distinct 400 so misconfiguration is never masked as missing frames.Request formats
S3 (unchanged — today's alert-api requests behave byte-for-byte identically):
bucketis optional and falls back toTEMPORAL_API_S3_BUCKET, as before. An explicit"source": "s3"is equivalent to omitting it on an s3-default server.Local (edge box configured with
TEMPORAL_API_FRAME_SOURCE=localandTEMPORAL_API_FRAMES_ROOT=/data/frames):Each frame resolves to
/data/frames/<frame>and is read in place.sourcemay be omitted when the server's default is alreadylocal(the expected edge setup).roi_xyxynand?verbose=truework identically for both sources.Error contract (local additions):
"source": "local"+"bucket": "..."400 invalid_request(bucket is not valid with local frames)"source": "local"override on a server withoutFRAMES_ROOT400 invalid_request(a local-default server without a root refuses to start)FRAMES_ROOTset but not a directory (typo, unmounted volume)400 invalid_request— distinct from per-frame 404., absolute,.., or escapes the root via symlink400 invalid_request404 frame_not_found(same as a missing S3 key)Producer invariants for local mode (documented in
api/README.md): publish frames atomically (write + rename — frames are read in place), and keep frame basename stems globally unique when the detection cache is enabled (the same invariant S3 keys already carry).Test Plan
make -C api test— 147 passed, 1 skipped (115 pre-existing tests pass unmodified — back-compat proof; 32 new tests cover the resolver, settings, schema, and route)make -C api lint/make -C api format— clean..(escaping and non-escaping), symlink escaping the root, empty/.frames